package be.nepherte.movmanager.client.ui.components;

import java.util.ArrayList;
import java.util.List;

import be.nepherte.movmanager.client.MovManager;
import be.nepherte.movmanager.client.exceptions.NoSuchGenreException;
import be.nepherte.movmanager.domain.Genre;
import be.nepherte.movmanager.domain.Movie;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.TextBox;

/**
 * This class represents a panel featuring title, rating, watched and genre
 * filtering or any combination of the previous.
 * 
 * <p>
 * Copyright © 2010 Bart Verhoeven
 * </p>
 * 
 * <p>
 * This file is part of MovManager.
 * </p>
 * 
 * <p>
 * MovManger is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * </p>
 * 
 * <p>
 * MovManger is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * </p>
 * 
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * MovManger. If not, see http://www.gnu.org/licenses/.
 * </p>
 * 
 * @author	Bart Verhoeven
 * @version 0.1
 */
public class FilterPanel extends Composite implements ClickHandler {
	/**A panel holding all the filters and the navigation arrows.*/
	private final HorizontalPanel pnlFilter = new HorizontalPanel();
	/**A text field for a title filter.*/
	private final TextBox txtTitle = new TextBox();
	/**A list box with all the genres.*/
	private final GenreListBox lstGenres = new GenreListBox();
	/**A list box with ratings.*/
	private final RatingBox lstRatings = new RatingBox();
	/**A check box to filter watched movies.*/
	private final CheckBox chckWatched = new CheckBox();
	/**A filter button.*/
	private final Button btnFilter = new Button(MovManager.constants.buttonFilter());
	/**Whether filter is active.*/
	private boolean isActive = false;
	
	/**
	 * Creates a new filter panel.
	 */
	public FilterPanel() {
		super();
		initFilterPanel();
	}
	
	/**
	 * Initializes the filter panel.
	 */
	private void initFilterPanel() {
		formatFilterPanel();
		txtTitle.addClickHandler(this);
		pnlFilter.add(txtTitle);
		pnlFilter.add(lstGenres);
		pnlFilter.add(lstRatings);
		pnlFilter.add(chckWatched);
		pnlFilter.add(btnFilter);
		initWidget(pnlFilter);
	}
	
	/**
	 * Formats the filter panel.
	 */
	private void formatFilterPanel() {
		setToolTips();
		pnlFilter.setSpacing(10);
		pnlFilter.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
		btnFilter.setSize("80px", "22px");
	}
	
	/**
	 * Sets tool tips for widgets.
	 */
	private void setToolTips() {
		txtTitle.setTitle("Set partial movie title");
		lstGenres.setTitle("Set movie genre");
		lstRatings.setTitle("Set minimum rating");
		chckWatched.setTitle("Exclude watched movies");
		btnFilter.setTitle("Filter movie list");
	}
	
	/**
	 * Resets all active filters.
	 */
	public void reset() {
		txtTitle.setText("Any movie title...");
		lstGenres.setItemSelected(0, true);
		lstRatings.setItemSelected(0, true);
		chckWatched.setValue(false);
		isActive = false;
	}
	
	/**
	 * Filters the given movie list according to the active filters.
	 * 
	 * @param	movies
	 * 			list of movies to be filtered
	 * @return	filtered movie list
	 */
	public List<Movie> filter(List<Movie> movies) {
		List<Movie> filteredMovies = new ArrayList<Movie>();
		
		// values of all filters
		String title = txtTitle.getText();
		String genreString = lstGenres.getValue(lstGenres.getSelectedIndex());
		String ratingString = lstRatings.getValue(lstRatings.getSelectedIndex());
		boolean hasWatched = chckWatched.getValue();
		
		// See what filters are active
		boolean titleActive = !title.equals("Any movie title...") && !title.equals("") ? true : false;
		boolean genreActive = genreString.equals("Any Genre") ? false : true;
		boolean ratingActive = ratingString.equals("Any Rating") ? false : true;
		
		Genre genre = null;
		Double rating = null;
		
		if (genreActive)
			try {
				genre = Genre.getGenre(genreString);
			} catch (NoSuchGenreException e) {
				// should not happen: lstGenres is filled with with Genre.values() so only known genres are used 
			}
		if (ratingActive)
			rating = Double.parseDouble(ratingString.substring(0, ratingString.length()-1)); // rating is active so no parse error will occur
		
		if (titleActive || genreActive || ratingActive || hasWatched) {
			for (Movie movie: movies)
				if (matchesFilter(movie, titleActive, title, genreActive, genre, ratingActive, rating, hasWatched))
					filteredMovies.add(movie);
		} else {
			isActive = false;
			return movies;
		}
		
		isActive = true;
		return filteredMovies;
	}
	
	/**
	 * Whether the filter panel is active or not.
	 */
	public boolean isActive() {
		return isActive;
	}

	/**
	 * Verifies whether the given movie matches all the selected filter values.
	 * Returns true if the given movie matches all filters, false otherwise
	 * 
	 * @param	movie
	 * 			to movie to be verified
	 * @param	titleActive
	 * 			whether the title filter is active
	 * @param	selectedTitle
	 * 			the title filter value
	 * @param	genreActive
	 * 			whether the genre filter is active
	 * @param	selectedGenre
	 * 			the genre filter value
	 * @param	ratingActive
	 * 			whether the rating filter is active
	 * @param	selectedRating
	 * 			the rating filter value
	 * @param	excludedActive
	 * 			whether the watched movie filter is active
	 */
	private boolean matchesFilter(Movie movie, boolean titleActive, String selectedTitle, boolean genreActive, Genre selectedGenre, boolean ratingActive, Double selectedRating, boolean excludedActive) {
		// title filter is active
		if (titleActive)
			if (!movie.getTitle().toLowerCase().contains(selectedTitle.toLowerCase()))
				return false;
		
		// genre filter is active
		 if (genreActive) {
			 boolean genreMatched = false;
			 for (Genre genreMovie : movie.getGenres()) {
				 if (genreMovie.equals(selectedGenre)) {
					 genreMatched = true;
				 	 break;
				 }
			 }
			 if (!genreMatched)
				 return false;
		 }
		
		// rating filter is active
		if (ratingActive)
			if (movie.getRating() < selectedRating)
				return false;
		
		// exclude filter is active
		if (excludedActive)
			if (movie.hasWatched())
				return false;
		
		return true;
	}

	/**
	 * Called when a click event is fired by one of the clickable components in
	 * this tab.
	 */
	@Override
	public void onClick(ClickEvent event) {
		// the title text box has been clicked
		if (event.getSource() == txtTitle)
			if (txtTitle.getText().equals("Any movie title..."))
				txtTitle.setText("");
		
	}
	
	/**
	 * Sets the action to be performed when the filter button has been clicked.
	 * 
	 * @param	h
	 * 			click handler
	 */
	public void setFilterAction(ClickHandler h) {
		btnFilter.addClickHandler(h);
	}
}